/*
 * @Author: Tyler
 * @Date: 2025-10-15 16:31:25
 * @Description: 
 * Copyright (c) 2025 by User/Corp, All Rights Reserved.
 */
#include "../include/LossFunction.h"
#include <cmath>
#include <stdexcept>

using namespace std;

// Mean Squared Error Loss Implementation
double MeanSquaredError::compute(double predicted, double actual) const {
    // predicted: output of the neural network
    // actual: true label
    return 0.5 * (predicted - actual) * (predicted - actual);
}
double MeanSquaredError::derivative(double predicted, double actual) const {
    // predicted: output of the neural network
    // actual: true label
    return predicted - actual;
}
Tensor<double, 1> MeanSquaredError::computeToTensor(const Tensor<double, 1>& predicted, const Tensor<double, 1>& actual) const {
    #ifdef USE_USER_DEFINED_TENSOR 
        if (predicted.getSize() != actual.getSize()) {
            throw invalid_argument("Predicted and actual vectors must have the same size.");
        }
        Tensor<double, 1> loss(predicted.getSize());
        for (int i = 0; i < predicted.getSize(); ++i) {
            loss(i) = 0.5 * (predicted(i) - actual(i)) * (predicted(i) - actual(i));
        }
    #endif
    #ifdef USE_EIGEN_TENSOR 
        if (predicted.size() != actual.size()) {
            throw invalid_argument("Predicted and actual vectors must have the same size.");
        }
        Tensor<double, 1> loss(predicted.size());
        for (int i = 0; i < predicted.size(); ++i) {
            loss(i) = 0.5 * (predicted(i) - actual(i)) * (predicted(i) - actual(i));
        }
    #endif
    return loss;
}
Tensor<double, 1> MeanSquaredError::derivativeToTensor(const Tensor<double, 1>& predicted, const Tensor<double, 1>& actual) const {
    #ifdef USE_USER_DEFINED_TENSOR 
        if (predicted.getSize() != actual.getSize()) {
            throw invalid_argument("Predicted and actual vectors must have the same size.");
        }
        Tensor<double, 1> grad(predicted.getSize());
        for (int i = 0; i < predicted.getSize(); ++i) {
            grad(i) = predicted(i) - actual(i);
        }
    #endif
    #ifdef USE_EIGEN_TENSOR 
        if (predicted.size() != actual.size()) {
            throw invalid_argument("Predicted and actual vectors must have the same size.");
        }
        Tensor<double, 1> grad(predicted.size());
        for (int i = 0; i < predicted.size(); ++i) {
            grad(i) = predicted(i) - actual(i);
        }
    #endif
    return grad;
}
// Binary Categorical Crossentropy Loss Implementation
double CategoricalCrossentropy::compute(double predicted, double actual) const {
    return actual * log(predicted) + (1 - actual) * log(1 - predicted);
}
double CategoricalCrossentropy::derivative(double predicted, double actual) const {
    return predicted - actual;
}
Tensor<double, 1> CategoricalCrossentropy::computeToTensor(const Tensor<double, 1>& predicted, const Tensor<double, 1>& actual) const {
    Tensor<double, 1> loss(predicted.getSize());
    for (int i = 0; i < predicted.getSize(); ++i) {
        loss(i) = actual(i) * log(predicted(i)) + (1 - actual(i)) * log(1 - predicted(i));
    }
    return loss;
}
Tensor<double, 1> CategoricalCrossentropy::derivativeToTensor(const Tensor<double, 1>& predicted, const Tensor<double, 1>& actual) const {
    // ToDO: Check the logic of the derivative
    Tensor<double, 1> grad(predicted.getSize());
    for (int i = 0; i < predicted.getSize(); ++i) {
        grad(i) = predicted(i) - actual(i);
    }
    return grad;
}

// Factory Implementation
unique_ptr<LossFunction> LossFactory::create(const string& name) {
    if (name == "mse") {
        return make_unique<MeanSquaredError>();
    }
    return nullptr;
}
const string& LossFactory::getName(const LossFunction* func) {
    if (dynamic_cast<const MeanSquaredError*>(func)) {
        static const string name = "mse";
        return name;
    }
    if (dynamic_cast<const CategoricalCrossentropy*>(func)) {
        static const string name = "categorical_crossentropy";
        return name;
    }
    static const string unknown = "unknown";
    return unknown;
}
